﻿/// <reference path="_namespace.js" />
/// <reference path="GraphicsBase.js" />
/// <reference path="../math/Matrix.js" />
/// <reference path="../math/Vector3.js" />
/// <reference path="RenderFace.js" />
/// <reference path="Line.js" />
/// <reference path="Viewport.js" />
/// <reference path="Timer.js" />
/// <reference path="Light.js" />
/// <reference path="RenderStateManager.js" />
/// <reference path="DeviceTypes.js"/>

/*****************************************************************************
Ajax library class definition. Kevin Robinson 2006.
Class: ajax.drawing.Device3D
/*****************************************************************************/

ajax.lang.addNamespace("ajax.drawing");

ajax.lang.imports("ajax.drawing.GraphicsBase");
ajax.lang.imports("ajax.math.Matrix");
ajax.lang.imports("ajax.drawing.RenderFace");
ajax.lang.imports("ajax.drawing.Line");
ajax.lang.imports("ajax.drawing.Viewport");
ajax.lang.imports("ajax.drawing.Timer");
ajax.lang.imports("ajax.drawing.Light");
ajax.lang.imports("ajax.drawing.RenderStateManager");
ajax.lang.imports("ajax.drawing.DeviceTypes");

//-----------------------------------------------------------------------------
// DrawingObject class
ajax.drawing.Device3D = function(doc, parentNode, layerName, w, h, deviceType, graphicsScale)
{    
    ajax.drawing.Device3D.superConstructor.call(this, doc, parentNode, layerName, w, h);   
    	
	graphicsScale           = typeof(graphicsScale) == "undefined" ? 1 : graphicsScale;
	
	this._graphicsScale     = graphicsScale;
	this.deviceType         = deviceType;
	this.renderFaceBuffer   = new Array();
	this.lights             = new Array();
	this.transform          = new ajax.drawing.Device3D.Transforms();	
	this.facesSent          = 0;	
	this.timer              = new ajax.drawing.Timer();
	this.renderState        = new ajax.drawing.RenderStateManager();
	
	// Stuff populated after load
	this.viewport           = null;
	this.graphics           = null;
	// Talking of which...
	this.getEvent(this.window, "onLoad").addListener(this.onLoadHandler);
	this.getEvent(this.window, "onGraphicsCreated");	
	
	// Some of the graphics devices may not clip to this size, so we'd better make sure that our graphics
    // layer does not scroll...  
    this.layerStyle.overflow = "hidden";    
    this.layerStyle.clip = "rect(0px "+(this.width)+"px "+(this.height)+"px 0px)"; // Doesn't seem to help...
    
}

//-----------------------------------------------------------------------------
ajax.lang.extend("ajax.drawing.Device3D", "ajax.components.Container");
if ($getDesignMode())
    ajax.drawing.Device3D.prototype = ajax.components.Container.prototype;
//-----------------------------------------------------------------------------


// Public methods
//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.cullBackFaces = function(renderFaces)
{///<summary>Culls the back faces</summary>
 ///<param name="renderFaces" type="Array" elementType="ajax.drawing.RenderFace"/>    
    var rs = this.renderState;
       
    rs = ajax.drawing.RenderStateManager.cast(rs);
    // Mesh culling can only be LESS culling than the renderstate. If the rs says none, then that's it.
    if (rs.cullMode == ajax.drawing.CullModes.None)
        return;
    
    // Loop through all faces and set the face to be invisible
    // if the new face normal is facing away from the origin
    for (var i = 0; i < renderFaces.length; i++)
    {
        var face = renderFaces[i];
        face = ajax.drawing.RenderFace.cast(face);
        
        // early skip - check renderface culling
        if (face.cullMode == ajax.drawing.CullModes.None)
            continue;
        
        // Dot the transformedNormal with a vector from us to a point on the triangle
        var cosAngle = face.normal.dot(face.cameraSpaceVertA);        
        var sng = ajax.math.Vector3.sign(cosAngle);
        
        if (
            (rs.cullMode == ajax.drawing.CullModes.AntiClockwise && sng == 1)
            ||
            (rs.cullMode == ajax.drawing.CullModes.Clockwise && sng == -1)
            )
        {
            // this face is backfacing so remove it            
            renderFaces.removeAt(i--);            
        }
    }
}

//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.nearZPolyClip = function(face, renderFaces)
{///<summary>Will clip (and generate extra geometry if needed) to the near Z viewing value</summary>
 ///<param name="face" type="ajax.drawing.RenderFace"/>
 ///<param name="renderFaces" type="Array" elementType="ajax.drawing.RenderFaces"/>
 
    face = ajax.drawing.RenderFace.cast(face);
    var viewport = ajax.drawing.Viewport.cast(this.viewport);
    
    // Here we need to clip individual Polygons on a per vertex basis as they approach the camera
    // We will split one poly into 2 if we need to.
    // You see the last line of code of this method where we add the newly created face (if needed).
    
    var iNumVertsIn = 0;
    var bVA = false;
    var bVB = false;
    var bVC = false;
    
    var nearZClip = viewport.zNear;
    
    // Check vert A
    if (face.cameraSpaceVertA.z > nearZClip)
    {
        iNumVertsIn++;
        bVA = true;
    }
    // Check vert B
    if (face.cameraSpaceVertB.z > nearZClip)
    {
        iNumVertsIn++;
        bVB = true;
    }
    // Check vert C
    if (face.cameraSpaceVertC.z > nearZClip)
    {
        iNumVertsIn++;
        bVC = true;
    }
    
    // If num verts In == 0 or 3 then we just return as its either TOTALLY in the frustum or TOTALLY outside.
    if (iNumVertsIn == 0 || iNumVertsIn == 3)
        return;
    
    
    // Create temp storage as we need to clip this poly
    var tempVerts = new Array(3);
    
    if (iNumVertsIn == 1)
    {
        // 1 vert in frustum
        if (bVA)
        {
            // Use the copy constructor as these are REFERENCE type variables
            // So assigning one to the other will NOT copy the values across
            // to a new instance
            tempVerts[0] = new ajax.math.Vector3(); tempVerts[0].copyValuesFrom(face.cameraSpaceVertA);
            tempVerts[1] = new ajax.math.Vector3(); tempVerts[1].copyValuesFrom(face.cameraSpaceVertB);
            tempVerts[2] = new ajax.math.Vector3(); tempVerts[2].copyValuesFrom(face.cameraSpaceVertC);                        
        }
        else if (bVB)
        {
            tempVerts[0] = new ajax.math.Vector3(); tempVerts[0].copyValuesFrom(face.cameraSpaceVertB);
            tempVerts[1] = new ajax.math.Vector3(); tempVerts[1].copyValuesFrom(face.cameraSpaceVertA);
            tempVerts[2] = new ajax.math.Vector3(); tempVerts[2].copyValuesFrom(face.cameraSpaceVertC);
        }
        else if (bVC)
        {
            tempVerts[0] = new ajax.math.Vector3(); tempVerts[0].copyValuesFrom(face.cameraSpaceVertC);
            tempVerts[1] = new ajax.math.Vector3(); tempVerts[1].copyValuesFrom(face.cameraSpaceVertB);
            tempVerts[2] = new ajax.math.Vector3(); tempVerts[2].copyValuesFrom(face.cameraSpaceVertA);
        }
        
        // Parametric line stuff
        // p = v0 + v01*t
        
        // First vert point...
        var v01 = ajax.math.Vector3.subtract(tempVerts[1], tempVerts[0]);
        var t1  = ( (nearZClip - tempVerts[0].z)/v01.z );
        
        var newz1 = nearZClip;
        var newx1 = tempVerts[0].x + v01.x * t1;
        var newy1 = tempVerts[0].y + v01.y * t1;
        
        // Second Vert point...
        var v02 = ajax.math.Vector3.subtract(tempVerts[2], tempVerts[0]);
        var t2 = ( (nearZClip - tempVerts[0].z) / v02.z );
        
        var newz2 = nearZClip;
        var newx2 = tempVerts[0].x + v02.x * t2;
        var newy2 = tempVerts[0].y + v02.y * t2;
        
        // Assign calculated vert1
        tempVerts[1].x = newx1;
        tempVerts[1].y = newy1;
        tempVerts[1].z = newz1;
        
        // Assign calculated vert2
        tempVerts[2].x = newx2;
        tempVerts[2].y = newy2;
        tempVerts[2].z = newz2;
        
        if (bVA)
        {
            face.cameraSpaceVertB = tempVerts[1];
            face.cameraSpaceVertC = tempVerts[2];
        }
        else if (bVB)
        {
            face.cameraSpaceVertA = tempVerts[1];
            face.cameraSpaceVertC = tempVerts[2];
        }
        else if (bVC)
        {
            face.cameraSpaceVertB = tempVerts[1];
            face.cameraSpaceVertA = tempVerts[2];
        }
        
        // Recompute the screen coords
        face.reComputeCameraToScreen(viewport);
            
    }
    else if (iNumVertsIn == 2)
    {
        // 2 Verts in frustum
        if (!bVA)
        {
            tempVerts[0] = new ajax.math.Vector3(); tempVerts[0].copyValuesFrom(face.cameraSpaceVertB);
            tempVerts[1] = new ajax.math.Vector3(); tempVerts[1].copyValuesFrom(face.cameraSpaceVertC);
            tempVerts[2] = new ajax.math.Vector3(); tempVerts[2].copyValuesFrom(face.cameraSpaceVertA);
        }
        else if (!bVB)
        {
            tempVerts[0] = new ajax.math.Vector3(); tempVerts[0].copyValuesFrom(face.cameraSpaceVertA);
            tempVerts[1] = new ajax.math.Vector3(); tempVerts[1].copyValuesFrom(face.cameraSpaceVertC);
            tempVerts[2] = new ajax.math.Vector3(); tempVerts[2].copyValuesFrom(face.cameraSpaceVertB);
        }
        else if (!bVC)
        {
            tempVerts[0] = new ajax.math.Vector3(); tempVerts[0].copyValuesFrom(face.cameraSpaceVertA);
            tempVerts[1] = new ajax.math.Vector3(); tempVerts[1].copyValuesFrom(face.cameraSpaceVertB);
            tempVerts[2] = new ajax.math.Vector3(); tempVerts[2].copyValuesFrom(face.cameraSpaceVertC);
        }
        
        // Parametric line stuff
        // p = v0 + v01*t
        
        //Calculate first point
        var v01 = ajax.math.Vector3.subtract(tempVerts[2], tempVerts[0]);
        
        var t1 = ( (nearZClip - tempVerts[0].z) / v01.z );
        
        var newz1 = nearZClip;
        var newx1 = tempVerts[0].x + v01.x * t1;
        var newy1 = tempVerts[0].y + v01.y * t1;
        
        // Calculate Second point
        var v02 = ajax.math.Vector3.subtract(tempVerts[2], tempVerts[1]);
        
        var t2 = ( (nearZClip - tempVerts[1].z) / v02.z );
        
        var newz2 = nearZClip;
        var newx2 = tempVerts[1].x + v02.x * t2;
        var newy2 = tempVerts[1].y + v02.y * t2;
        
        // Assign first point
        tempVerts[2].x = newx1;
        tempVerts[2].y = newy1;
        tempVerts[2].z = newz1;
        
        if (!bVA)        
            face.cameraSpaceVertA = tempVerts[2];
        else if (!bVB)
            face.cameraSpaceVertB = tempVerts[2];
        else if (!bVC)
            face.cameraSpaceVertC = tempVerts[2];
            
        // Recompute the screen coords
        face.reComputeCameraToScreen(viewport);
        
        // We now need to create a new face, based on !!
        // v1 = new1
        // v2 = new2
        // v3 = tempVerts[1]
        // and the existing face's normal and color        
        var newFace = ajax.drawing.RenderFace.create(
            tempVerts[2],
            new ajax.math.Vector3(newx2, newy2, newz2),
            tempVerts[1],
            face.color,
            face.normal,
            viewport
            );

        // Use the same zBias
        newFace.zBias = face.zBias;        
        
        // Use the same culling and filling of the face we were sent
        newFace.cullMode = face.cullMode;
        newFace.fillMode = face.fillMode;
        newFace.lighting = face.lighting;
        newFace.lights   = face.lights;
        
        
        // Add the new face we created dynamically
        renderFaces[renderFaces.length] = newFace;                            
    }
}


//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.cullFacesToFrustum = function(renderFaces)
{///<summary>Culls faces to current viewing Frustum</summary>
 ///<param name="renderFaces" type="Array" elementType="ajax.drawing.RenderFace"/>
    
    var renderState = ajax.drawing.RenderStateManager.cast(this.renderState);
    // Early out
    if (renderState.faceFrustumCullMode == ajax.drawing.FrustumCullModes.None)
        return;
    
    var viewport = ajax.drawing.Viewport.cast(this.viewport);
    var zNear = viewport.zNear;
    var zFar  = viewport.zFar;
    
    var xACompare = 0;
    var xBCompare = 0;
    var xCCompare = 0;
    
    var yACompare = 0;
    var yBCompare = 0;
    var yCCompare = 0;
    
    for (var i = 0; i < renderFaces.length; i++)
    {
        var face = ajax.drawing.RenderFace.cast(renderFaces[i]);
        
        // Perform near / far clipping first - if none of the vertices are within z limits, clip it!
        if (!
            ((face.cameraSpaceVertA.z > zNear || face.cameraSpaceVertB.z > zNear || face.cameraSpaceVertC.z > zNear)
            &&
            (face.cameraSpaceVertA.z < zFar || face.cameraSpaceVertB.z < zFar || face.cameraSpaceVertC.z < zFar))
           )
        {
            // Clip it!
            renderFaces.removeAt(i--);
            continue;
        }
        
        // Is that it?
        if (renderState.faceFrustumCullMode == ajax.drawing.FrustumCullModes.ZOnly)
            continue;
            
        // Ok so XYZ selected...
        
        // Do X clipping - precompute x comparison ranges
        xACompare = (viewport.halfWidth * face.cameraSpaceVertA.z) / viewport.viewingDistance;
        xBCompare = (viewport.halfWidth * face.cameraSpaceVertB.z) / viewport.viewingDistance;
        xCCompare = (viewport.halfWidth * face.cameraSpaceVertC.z) / viewport.viewingDistance;
        if (!
            ((face.cameraSpaceVertA.x > -xACompare || face.cameraSpaceVertB.x > -xBCompare || face.cameraSpaceVertC.x > -xCCompare)
            &&
            (face.cameraSpaceVertA.x < xACompare || face.cameraSpaceVertB.x < xBCompare || face.cameraSpaceVertC.x < xCCompare))
           )
        { // No verts fall inside the frustum X
            // Clip it!
            renderFaces.removeAt(i--);
            continue;
        }
        
        // Do Y clipping
        yACompare = (viewport.halfHeight * face.cameraSpaceVertA.z) / viewport.viewingDistance;
        yBCompare = (viewport.halfHeight * face.cameraSpaceVertB.z) / viewport.viewingDistance;
        yCCompare = (viewport.halfHeight * face.cameraSpaceVertC.z) / viewport.viewingDistance;
        if (!
            ((face.cameraSpaceVertA.y > -yACompare || face.cameraSpaceVertB.y > -yBCompare || face.cameraSpaceVertC.y > -yCCompare)
            &&
            (face.cameraSpaceVertA.y < yACompare || face.cameraSpaceVertB.y < yBCompare  || face.cameraSpaceVertC.y < yCCompare))
           )
        {// No verts fall inside the frustum Y
            // Clip It!!
            renderFaces.removeAt(i--);
            continue;
        }
        
        // Still here? then it's a keeper, if enabled, do NearZPolyClip
        if (renderState.nearZPolyClipEnabled)
        {            
            this.nearZPolyClip(face, renderFaces); 
        }           
    }    
}

//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.onLoadHandler = function(e)
{
    this.createGraphics();
}

//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.createGraphics = function(deviceType)
{    
    if (this.graphics != null)
        this.graphics.dispose();
        
    this.deviceType = typeof(deviceType) != "undefined" ? deviceType : this.deviceType;
    // Create graphics device depending on the device type selected.    
    var deviceTypeString = "";
    switch (this.deviceType)
    {
        case ajax.drawing.DeviceTypes.Native:
            deviceTypeString = "ajax.drawing.Graphics";
            break;
        /*
        case deviceTypes.Java:
            deviceTypeString = "ajax.drawing.GraphicsJava";
            break;
        */
        case ajax.drawing.DeviceTypes.Canvas:
            deviceTypeString = "ajax.drawing.GraphicsCanvas";
            break;
        default:
            deviceTypeString = "ajax.drawing.Graphics";
            break;
    }    
    
    // Create graphics, set the scale and create the viewport    
    this.graphics = ajax.lang.safeNew(deviceTypeString, this.doc, this.layer, this.width, this.height);      
    this.graphics.setScale(this._graphicsScale);
    // if the graphics renderer does not support scaling, it will tell you - so pass it's answer to the viewport constructor
    this.viewport = new ajax.drawing.Viewport(0, 0, this.width, this.height, 800, 640/480, 0.1, 10000, this.graphics.getScale());
    
    this.onGraphicsCreated.call(this);
}

//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.clear = function(colour)
{///<param name="colour" type="ajax.drawing.Colour"/>    
    this.graphics.clear(colour);
}

//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.beginScene = function()
{                       
    // Reset FacesSent
    this.facesSent = 0;
    
    // Reset RenderFaceBuffer
    this.renderFaceBuffer = new Array();         
    
}

//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.endScene = function()
{    
    // Cull Backfaces
    this.cullBackFaces(this.renderFaceBuffer);  
    // Cull to frustum        
    this.cullFacesToFrustum(this.renderFaceBuffer);    
    
    var rs = this.renderState;
    rs = ajax.drawing.RenderStateManager.cast(rs);
    
    // Compute face z values and sort.
    if (rs.zBufferEnabled)
    {        
        // Sort function, takes two render faces.
        function srt(a, b)
        {
            if (a.averageZ > b.averageZ)
                return -1;            
            return 1;
        }     
        
        // Compute the average z for all Renderfaces in our collection             
        for(var i = 0; i < this.renderFaceBuffer.length; i++)
        {
            var face = this.renderFaceBuffer[i];
            face = ajax.drawing.RenderFace.cast(face);
            face.computeAverageZ();                                               
        }
        // now SORT the render face buffer
        this.renderFaceBuffer.sort(srt);                                  
    }
    
    // Clients can turn off rendering to see the cost of all computations without the actual drawing.
    if (this.renderState.renderingEnabled)
        this.drawRenderFaces();
}

//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.present = function()
{         
    //this.graphics.context.stroke();
    // this is where we flip the Back & Front buffer        
    this.graphics.repaint();           
}

//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.getFaceInfo = function()
{
    var s = "F Sent: " + this.facesSent + ", F Rendered: " + this.renderFaceBuffer.length;
    return s;
}

//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.drawFaceInfo = function(x, y)
{
    var g = ajax.drawing.GraphicsBase.cast(this.graphics);
    g.drawString(this.getFaceInfo(), x, y);    
}

//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.drawFaces = function(faces, vb, radius, zBias)
{///<summary>Called by meshes, send in the faces, vertex buffer and radius and this will
 ///  - Create a cameraSpace Matrix for this object (world * view)
 ///  - Do Object culling (is the object even in the view frustum?)
 ///  - Transforms all verts (from vertex buffer)
 ///  - Transforms all face normals
 ///  - Computes screen Verts
 ///  - Calls appendRenderFaces, creating RenderFaces for each mesh face.</summary>
         
	// increment the faces were have been sent
	this.facesSent += faces.length;

	// Set the Total Matrix = World * View	
    var cameraSpaceMatrix = ajax.math.Matrix.multiply(this.transform.world, this.transform.view);
        
    var renderState = ajax.drawing.RenderStateManager.cast(this.renderState);
    var viewport    = ajax.drawing.Viewport.cast(this.viewport);
        
    // If Object / Frustum culling is enabled, do that here.
    if (renderState.objectFrustumCullMode != ajax.drawing.FrustumCullModes.None)
    {
        // Note: this method ignores settings - if they choose anything other than none, we do them all!        
        if (!viewport.objectInsideFrustum(new ajax.math.Vector3(), radius, cameraSpaceMatrix))
            return; // BIG early out!!
    }
    	
	// With most of the wastage out the way, we compute 
	// - The camera verts 
	// - The Camera Normals
	// - The Screen Coordintates

	// Get a new Vertex buffer with the transformed vertices.
	var cameraVerts = cameraSpaceMatrix.transformVertices(vb);					

	// Get a new Normals buffer with the Transformed Face Normals (these are used in backface removal (optimal) 
	// as well as lighting - so they ARE important.
	var cameraNormals = cameraSpaceMatrix.transformFaceNormals(faces);					
				
    // TODO: isn't this a bit wasteful now? Don't you only need to compute the actual screenVerts if
    // we are definately going to render this (it could now be clipped).
	// Transform Camera to Screen
	var screenVerts = this.viewport.computeCameraToScreen(cameraVerts);

	// With all this computed, we add these faces to the renderFacebuffer
	this.appendRenderFaces(faces, cameraVerts, cameraNormals, screenVerts, zBias);

}

//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.appendRenderFaces = function(faces, cameraSpaceVerts, cameraSpaceNormals, screenSpaceVerts, zBias)
{    
    // Copy the current lights array & apply it to all renderfaces
    var lightsCopy = [];    
    for (var i = 0; i < this.lights.length; i++)
        lightsCopy[i] = this.lights[i];
        
    // We need to Append these Renderfaces to our list so that we can ultimately draw them
	for(var i = 0; i < faces.length; i++)
	{
		// Create new RenderFace		
		var newFace = new ajax.drawing.RenderFace(faces[i], cameraSpaceNormals[i], cameraSpaceVerts, screenSpaceVerts, zBias);
		// Copy some of our current renderstate data to this face.				
		newFace.cullMode = this.renderState.cullMode;
		newFace.fillMode = this.renderState.fillMode;
		newFace.lighting = this.renderState.lighting;
		newFace.lights   = lightsCopy;
		this.renderFaceBuffer[this.renderFaceBuffer.length] = newFace;
	}
}

//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.drawPolygon = function(verts)
{///<param name="verts" type="Array" elementType="ajax.math.Vector3"/>
    // there must be at least 3 lines to make a polygon
    if (verts.length < 3)
        alert("DrawPolygon must have at least 3 verts");            
        
    var g = ajax.drawing.GraphicsBase.cast(this.graphics);
    
    g.drawPolygon3(verts[0].x, verts[0].y, verts[1].x, verts[1].y, verts[2].x, verts[2].y);
    
    /*
    for (var i = 1; i < verts.length; i++)
    {
        // Forget index 0;
        if (i > 0)
        {
            var line = new ajax.drawing.Line(verts[i-1], verts[i]);
            this.drawClipLine(line, null); // pass without colour as that was set in our caller.
        }

    }
    // Finally draw the closing line
    var line = new ajax.drawing.Line(verts[0], verts[verts.length - 1]);
    this.drawClipLine(line, null); // pass without colour as that was set in our caller. 
    */
}

//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.fillPolygon = function(verts)
{///<param name="verts" type="Array" elementType="ajax.math.Vector3"/>
    // there must be at least 3 lines to make a polygon
    if (verts.length < 3)
        alert("FillPolygon must have at least 3 verts");
              
    this.graphics.fillPolygon3(
        verts[0].x, 
        verts[0].y,
        verts[1].x, 
        verts[1].y,
        verts[2].x, 
        verts[2].y
     );     
}

//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.drawPoints = function(verts)
{///<param name="verts" type="Array" elementType="ajax.math.Vector3"/>
    var g = ajax.drawing.GraphicsBase.cast(this.graphics);
    
    for (var i = 0; i < verts.length; i++)   
        g.fillRect(verts[i].x, verts[i].y, 4, 4);
        //g.fillEllipse(verts[i].x, verts[i].y, 1, 1);
    
}
//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.drawRenderFaces = function()
{			        
    var g = ajax.drawing.GraphicsBase.cast(this.graphics);    
    
    // Unroll this to optimise.
	// Draw each face we've been sent
	for (var i = 0; i < this.renderFaceBuffer.length; i++)
	{	    
	    var face = this.renderFaceBuffer[i];
	    face = ajax.drawing.RenderFace.cast(face);
	    
	    	    
		// Work out what color to draw this (lighting would be done here)
		var drawColor = face.computeColor(this);					
		g.setColor(drawColor);	 // set to graphics				                
        
        // Always use the renderface fillmode - the renderstate version is used as a default only.
        // switch(this.renderState.fillMode)
        switch(face.fillMode)                
        {
            case ajax.drawing.FillModes.Solid:
                this.fillPolygon(face.screenSpaceVerts);
                break;
            case ajax.drawing.FillModes.Wireframe:                                              
                this.drawPolygon(face.screenSpaceVerts);
                break;
            case ajax.drawing.FillModes.Point:
                this.drawPoints(face.screenSpaceVerts);
                break;            
        }        			
	}		
}

//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.drawClipLine = function(line, color)
{  
	if (this.clipLine(line, this.width, this.height))
		this.drawLine(line, color);
}

//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.drawLine = function(line, color)
{
    if (color) // color is now optional
        this.graphics.setColor(color);
    this.graphics.drawLine(line.pointA.x, line.pointA.y, line.pointB.x, line.pointB.y);
    
	// return success
	return true;
} 

//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.clipLine = function(line, width, height)
{
    return ajax.drawing.GraphicsBase.clipLine(line, width, height);
}

//-----------------------------------------------------------------------------

ajax.drawing.Device3D.prototype.toString = function()
{   
    return "ajax.drawing.Device3D";
}


//-----------------------------------------------------------------------------
ajax.drawing.Device3D.cast = function(obj)
{///<returns type="ajax.drawing.Device3D"/>
    return obj;
}



//-----------------------------------------------------------------------------
// Sub Classes
// CTransforms Class
ajax.drawing.Device3D.Transforms = function()
{
	this.world      = new ajax.math.Matrix();
	this.view       = new ajax.math.Matrix();
	this.projection = new ajax.math.Matrix();	
}
//-----------------------------------------------------------------------------
ajax.drawing.Device3D.Transforms.prototype = 
{
    world: ajax.math.Matrix.prototype,
    view: ajax.math.Matrix.prototype,
    projection: ajax.math.Matrix.prototype
}
//-----------------------------------------------------------------------------
ajax.drawing.Device3D.Transforms.cast = function(obj)
{///<returns type="ajax.drawing.Device3D.Transforms"/>
    return obj;            
}




// Public members for ms ajax (Device3D)
//-----------------------------------------------------------------------------
ajax.drawing.Device3D.prototype.lights             = Array.prototype;
ajax.drawing.Device3D.prototype.deviceType         = ajax.drawing.DeviceTypes.Canvas;
ajax.drawing.Device3D.prototype.renderFaceBuffer   = Array.prototype;
ajax.drawing.Device3D.prototype.transform          = ajax.drawing.Device3D.Transforms.prototype; 
ajax.drawing.Device3D.prototype.viewport           = ajax.drawing.Viewport.prototype;
ajax.drawing.Device3D.prototype.facesSent          = 0;	
ajax.drawing.Device3D.prototype.timer              = ajax.drawing.Timer.prototype;
ajax.drawing.Device3D.prototype.graphics           = ajax.drawing.GraphicsBase.prototype;
ajax.drawing.Device3D.prototype.renderState        = ajax.drawing.RenderStateManager.prototype;
